package com.spring.proxy.javassist;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewConstructor;

public class JavassistProxy {

	public static Object newProxyInstance(JavassistClassLoader classLoader, Class<?>[] interfaces, JavassistInvocationHandler h) {
		try {
			CtClass cc = generate(interfaces[0], h);
			Class c = cc.toClass();  
			//获取构造器  
	        Constructor constructor= c.getConstructor(JavassistInvocationHandler.class);  
	        //通过构造器实例化  
	       return constructor.newInstance(h);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	private static CtClass generate(Class<?> interfaceClazz, JavassistInvocationHandler h) throws Exception {
		String packageNameCurr = JavassistProxy.class.getPackage().getName();
		ClassPool pool = ClassPool.getDefault();  
		CtClass cc = pool.makeClass(packageNameCurr+".$JavassistProxy0");  
		//设置接口  
        CtClass interface1 = pool.get(interfaceClazz.getCanonicalName());  
        cc.setInterfaces(new CtClass[]{interface1});  
          
        //设置Field  
        CtField field = CtField.make("private com.spring.proxy.javassist.JavassistInvocationHandler h;", cc);  
        cc.addField(field);  
        
        CtClass stationClass = pool.get("com.spring.proxy.javassist.JavassistInvocationHandler");  
        CtClass[] arrays = new CtClass[]{stationClass};  
        CtConstructor ctc = CtNewConstructor.make(arrays,null,CtNewConstructor.PASS_NONE,null,null, cc);  
        //设置构造函数内部信息  
        ctc.setBody("{this.h=$1;}");  
        cc.addConstructor(ctc);  
        
        Method[] methods = interfaceClazz.getMethods();
		for (Method method : methods) {
			//创建方法  
			String returnType = method.getReturnType().getName();
			String methodName = method.getName();
			Parameter[] ps = method.getParameters();
			StringBuilder paramArray = new StringBuilder();
			StringBuilder paramType = new StringBuilder(",");
			for (Parameter parameter : ps) {
				paramType.append(parameter.getType().getName() + ".class" + ",");
				paramArray.append(parameter.getType().getName() + " " + parameter.getName() + ",");
			}
			if (paramType.length() != 0) {
				paramType.deleteCharAt(paramType.length() - 1);
			} else {
				paramType = new StringBuilder();
			}
	        CtMethod takeHandlingFee = CtMethod.make("public "+returnType+" "+methodName+"("+paramArray+") {}", cc);  
	        takeHandlingFee.setBody("{"+implMethod(method, h, interfaceClazz).toString()+"}");  
	        cc.addMethod(takeHandlingFee);  
		}
		return cc;
	}

	private static StringBuilder implMethod(Method method, JavassistInvocationHandler h, Class<?> interfaceClazz) {
		StringBuilder implMethod = new StringBuilder();
		String returnType = method.getReturnType().getName();

		Parameter[] ps = method.getParameters();
		StringBuilder paramType = new StringBuilder(",");
		StringBuilder params = new StringBuilder("Object[] param = new Object[" + ps.length + "];");
		int i = 0;
		for (Parameter parameter : ps) {
			paramType.append(parameter.getType().getName() + ".class" + ",");
			params.append("param[" + i + "]=" + parameter.getName() + ";");
			i++;
		}
		if (paramType.length() != 0) {
			paramType.deleteCharAt(paramType.length() - 1);
		} else {
			paramType = new StringBuilder();
		}
		StringBuilder meth = new StringBuilder("java.lang.reflect.Method m=null;");
		meth.append("try {");
		meth.append("m = Class.forName(\"" + interfaceClazz.getName() + "\").getMethod(\"" + method.getName() + "\"" + paramType + ");");
		implMethod.append("Object obj=null;");
		implMethod.append(params);
		implMethod.append(meth);
		if (void.class.getName().equals(returnType)) {
			implMethod.append("h.invoke(this,m, param);");
		} else {
			implMethod.append("obj=h.invoke(this,m, param);");
		}
		implMethod.append("} catch (Throwable e) {" + "			e.printStackTrace();" + "		}");
		if (!void.class.getName().equals(returnType)) {
			implMethod.append("return (" + returnType + ")obj;");
		}
		System.out.println(implMethod);
		return implMethod;
	}
}
